package com.boarsoft.boar.agent.log;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.AccessDeniedException;
import java.nio.file.NotDirectoryException;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.boarsoft.boar.agent.AgentConfig;
import com.boarsoft.common.util.DateUtil;
import com.boarsoft.common.util.FileUtil;
import com.boarsoft.common.util.StreamUtil;

public class LogDirMeta implements Serializable {
	private static final long serialVersionUID = -7673211006564414363L;
	private static final Logger log = LoggerFactory.getLogger(LogDirMeta.class);

	protected String dirPath;
	/** 日志存储，与elasticsearch中的index，mongodb中的collection对应 */
	protected String target = "logs";
	/** */
	protected String appPort;
	/** */
	protected String fileExt = ".log";
	/** 日志有效期，单位：天 */
	protected int logTerm = 30;
	/** */
	protected String appAddr;
	/** 文件写读与编排 */
	protected String charset = StandardCharsets.UTF_8.name();

	protected transient File idxFile;
	protected transient File dirFile;
	/** 是文件创建时间为key */
	protected transient Map<Long, LogFileMeta> fileMap = new ConcurrentHashMap<Long, LogFileMeta>();

	public void init() throws IOException {
		//
		dirFile = new File(dirPath);
		if (!dirFile.exists()) {
			log.warn("{} not exists", dirPath);
		} else if (!dirFile.isDirectory()) {
			throw new NotDirectoryException(dirPath.concat(" is not a directory"));
		} else if (!dirFile.canRead()) {
			throw new AccessDeniedException(dirPath.concat(" not exists"));
		}
		//
		String fp = new StringBuilder().append(AgentConfig.FILE_PATH)//
				.append("/logs/").append(appPort).append(".txt").toString();
		idxFile = new File(fp);
		if (!idxFile.exists()) {
			return;
		}
		//
		BufferedReader br = null;
		try {
			br = new BufferedReader(new InputStreamReader(//
					new FileInputStream(idxFile), charset));
			String ln = null;
			while ((ln = br.readLine()) != null) {
				try {
					String[] a = ln.split(",");
					File f = new File(a[0]);// 文件路径
					if (!f.exists()) {
						continue;
					}
					long ct = Long.parseLong(a[1]);// 文件创建时间
					// 此处传null表示要获取文件创建时间，并与文件比对
					LogFileMeta fm = this.getFileMeta(f);
					// 如果这个文件不存在或对应的创建时间不一致，则丢弃此记录
					if (ct == fm.getCreationTime()) {
						fm.setOffset(Long.parseLong(a[2])); // 当前读取偏移量
					}
				} catch (Exception e) {
					log.error("Error on read link: [{}]", ln, e);
				}
			}
		} finally {
			StreamUtil.close(br);
		}
	}

	public LogFileMeta getFileMeta(File f) {
		// 获取文件创建时间，并以此为key
		Long ct = null;
		try {
			ct = FileUtil.getCreationTime(f).toMillis();
		} catch (IOException e) {
			log.error("Error on get creation time of file {}", f.getAbsolutePath(), e);
		}
		LogFileMeta fm = fileMap.get(ct);
		if (fm == null) {
			synchronized (fileMap) {
				fm = fileMap.get(ct);
				if (fm == null) {
					fm = new LogFileMeta();
					fm.setCreationTime(ct);
					fm.setFile(f);
					fileMap.put(ct, fm);
				}
			}
		}
		if (!f.equals(fm.getFile())) {
			log.warn("{} is rolled between scans", f.getAbsolutePath());
			fm.setFile(f);
		}
		return fm;
	}

	public void remove(LogFileMeta fm) {
		fileMap.remove(fm.getCreationTime());
	}

	public void put(long ct, LogFileMeta fm) {
		fileMap.put(ct, fm);
	}

	/**
	 * 刷新本地索引文件
	 * 
	 */
	public void flush() {
		PrintStream ps = null;
		try {
			StringBuilder sb = new StringBuilder();
			for (Entry<Long, LogFileMeta> en : fileMap.entrySet()) {
				LogFileMeta m = en.getValue();
				File f = m.getFile();
				if (f == null) {
					continue;
				}
				sb.append(f.getAbsolutePath()).append(",").append(en.getKey())//
						.append(",").append(m.getOffset()).append(FileUtil.LINE_SEPARATOR);
			}
			if (!idxFile.exists()) {
				idxFile.createNewFile();
			}
			ps = new PrintStream(new FileOutputStream(idxFile), true, charset);
			ps.print(sb.toString());
		} catch (IOException e) {
			log.error("Error on flush index file {}", idxFile.getAbsolutePath(), e);
		} finally {
			StreamUtil.close(ps);
		}
	}

	/**
	 * 检查目录是否存在且可读
	 * 
	 * @return
	 */
	public boolean check() {
		return dirFile.exists() && dirFile.isDirectory() && dirFile.canRead();
	}

	/**
	 * 检查指定文件是否过期且自上一次读取后有更新
	 * 
	 * @param f
	 * @return
	 */
	public boolean check(LogFileMeta fm) {
		// if (!f.getName().endsWith(fileExt)) {
		// return false;
		// }
		File f = fm.getFile();
		if (f == null) {
			return false;
		}
		// 删除过期的日志
		Date d = DateUtil.addDay(new Date(), -logTerm);
		long t = f.lastModified();
		if (t < d.getTime()) {
			String p = f.getAbsolutePath();
			log.warn("Delete expired log file {}", p);
			if (f.isDirectory()) {
				FileUtil.deleteDirectory(p);
			} else {
				FileUtil.deleteFile(p);
			}
			fileMap.remove(fm.creationTime);
			return false;
		}
		// 比较文件更新时间和上一次处理的时间
		return t > fm.getLastTime() || f.length() != fm.getLength();
	}

	public int getLogTerm() {
		return logTerm;
	}

	public void setLogTerm(int logTerm) {
		this.logTerm = logTerm;
	}

	public String getDirPath() {
		return dirPath;
	}

	public void setDirPath(String dirPath) {
		this.dirPath = dirPath;
	}

	public File getDirFile() {
		return dirFile;
	}

	public void setDirFile(File dirFile) {
		this.dirFile = dirFile;
	}

	public String getFileExt() {
		return fileExt;
	}

	public void setFileExt(String fileExt) {
		this.fileExt = fileExt;
	}

	public String getAppPort() {
		return appPort;
	}

	public String getAppAddr() {
		return appAddr;
	}

	public void setAppAddr(String appAddr) {
		this.appAddr = appAddr;
	}

	public String getTarget() {
		return target;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	public String getCharset() {
		return charset;
	}

	public void setCharset(String charset) {
		this.charset = charset;
	}

}
